﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Numerics;

namespace Scheme.Net.Runtime.AST
{
    #region Datum

    public interface IEvaluable { }

    #endregion

    public class EofObject : IEvaluable { }

    #region Kinds of Data

    public class Number : IEvaluable
    {
        public INumber Value { get; private set; }

        public Number(INumber n) { Value = n; }

        public override string ToString()
        {
            return Value.ToString();
        }
    }

    public class Character : IEvaluable
    {
        public char Value { get; private set; }

        public Character(char c) { Value = c; }

        public static Character Parse(string s)
        {
            if (s.ToUpper() == "#\\SPACE")
                return new Character(' ');
            else if (s.ToUpper() == "#\\NEWLINE")
                return new Character('\n');
            else
                return new Character(s[2]);
        }

        public override string ToString()
        {
            if (Value == ' ') return "#\\SPACE";
            if ((Value == '\n') || (Value == '\r')) return "#\\NEWLINE";
            
            return string.Format("#\\{0}", Value);
        }
    }

    public class String : IEvaluable
    {
        public string Value { get; set; }

        public String(string s) { Value = s; }

        public static String Parse(string s)
        {
            return new String(s.Substring(1, s.Length - 2).Replace("\\\"", "\""));
        }

        public override string ToString()
        {
            return string.Format("\"{0}\"", Value);
        }
    }

    public class Symbol : IEvaluable
    {
        #region Singularity of Symbols

        private static Dictionary<string, Symbol> symTable;
        private static int symbolCounter;

        static Symbol()
        {
            symTable = new Dictionary<string, Symbol>();
            symbolCounter = 0; 
        }

        public static Symbol Lookup(string n)
        {
            if (symTable.ContainsKey(n))
                return symTable[n];
            else
            {
                var s = new Symbol(n);
                symTable[n] = s;
                return s;
            }
        }

        protected Symbol(string n) { Name = n; }

        #endregion

        #region The Unspecified Value

        private static Symbol unspecifiedInstance;

        public static Symbol Unspecified
        {
            get
            {
                if (unspecifiedInstance == null)
                    unspecifiedInstance = new Symbol("*DUMMY*");

                return unspecifiedInstance;
            }
        }

        #endregion

        #region The Undefined Value

        private static Symbol undefinedInstance;

        public static Symbol Undefined
        {
            get
            {
                if (undefinedInstance == null)
                    undefinedInstance = new Symbol("*DUMMY*");

                return undefinedInstance;
            }
        }

        #endregion

        #region Generated Symbols

        public static Symbol GenerateSymbol()
        {
            return Symbol.Lookup(string.Format("#:g{0}", symbolCounter++));
        }

        #endregion

        #region Booleans

        public static Symbol Boolean(bool value)
        {
            if (value)
                return Symbol.Lookup("#T");
            else
                return Symbol.Lookup("#F");
        }

        public static Symbol True
        {
            get { return Symbol.Lookup("#T"); }
        }

        public static Symbol False
        {
            get { return Symbol.Lookup("#F"); }
        }

        #endregion

        public string Name { get; private set; }

        public override string ToString()
        {
            if (this == Symbol.Unspecified)
                return "*UNSPECIFIED-VALUE*";
            if (this == Symbol.Undefined)
                return "*UNDEFINED-VALUE-DO-NOT-WANT*";
            return string.Format("{0}", Name);
        }
    }

    public class Pair : IEvaluable
    {
        #region Fields

        public IEvaluable Car { get; set; }
        public IEvaluable Cdr { get; set; }

        public IEnumerable<IEvaluable> Values
        {
            get
            {
                return EnumerableExtensions.Unfold<Pair, IEvaluable>(
                    p =>
                    {
                        if (p.IsNull())
                            return Option<Tuple<IEvaluable, Pair>>.None;
                        else
                            return Option<Tuple<IEvaluable, Pair>>.Some(Tuple.Create(p.Car, (Pair)p.Cdr));
                    }, 
                    this);
            }
        }

        public IEnumerable<IEvaluable> ValuesExpanded
        {
            get
            {
                return EnumerableExtensions.Unfold<IEvaluable, IEvaluable>(
                    p =>
                    {
                        if (p == null)
                            return Option<Tuple<IEvaluable, IEvaluable>>.None;
                        else
                        {
                            if (p.IsPair())
                            {
                                var pair = p as Pair;
                                if (pair.IsNull())
                                    return Option<Tuple<IEvaluable, IEvaluable>>.Some(Tuple.Create((IEvaluable)Pair.Empty, (IEvaluable)null));

                                return Option<Tuple<IEvaluable, IEvaluable>>.Some(Tuple.Create(pair.Car, pair.Cdr));
                            }
                            else
                                return Option<Tuple<IEvaluable, IEvaluable>>.Some(Tuple.Create(p, (IEvaluable)null));
                        }
                    },
                    this);
            }
        }

        #endregion

        #region CADR, CDDR etc

        public IEvaluable NthCdr(int n)
        {
            var result = this;
            while (n > 0)
            {
                result = (Pair)result.Cdr;
                n--;
            }

            return result;
        }

        public IEvaluable Cadr
        {
            get
            {
                return ((Pair)Cdr).Car;
            }
        }

        public IEvaluable Caar
        {
            get
            {
                return ((Pair)Car).Car;
            }
        }

        public IEvaluable Cddr
        {
            get
            {
                return ((Pair)Cdr).Cdr;
            }
        }

        public IEvaluable Caddr
        {
            get
            {
                return ((Pair)((Pair)Cdr).Cdr).Car;
            }
        }

        public IEvaluable Cadddr
        {
            get
            {
                return ((Pair)((Pair)((Pair)Cdr).Cdr).Cdr).Car;
            }
        }

        public IEvaluable Cdddr
        {
            get
            {
                return ((Pair)((Pair)Cdr).Cdr).Cdr;
            }
        }

        public IEvaluable Caadr
        {
            get
            {
                return ((Pair)((Pair)Cdr).Car).Car;
            }
        }

        public IEvaluable Cdadr
        {
            get
            {
                return ((Pair)((Pair)Cdr).Car).Cdr;
            }
        }

        #endregion

        #region Construction

        protected Pair()
        {
            Car = null;
            Cdr = null;
        }

        private static Pair emptyInstance;

        public static Pair Empty
        {
            get
            {
                if (emptyInstance == null)
                    emptyInstance = new Pair();

                return emptyInstance;
            }
        }

        public Pair(IEvaluable car, IEvaluable cdr)
        {
            Car = car;
            Cdr = cdr;
        }

        public static Pair CreateDottedList(IEnumerable<IEvaluable> list, IEvaluable tail)
        {
            var result = list.FoldRight((a, d) => new Pair(d, a), tail);
            return (Pair)result;
        }

        public static Pair CreateList(IEnumerable<IEvaluable> list)
        {
            var result = list.FoldRight((a, d) => new Pair(d, a), Pair.Empty);
            return result;
        }

        #endregion

        #region Dotted Pairs

        public bool IsDottedPair
        {
            get { return ValuesExpanded.Last() != Pair.Empty; }
        }

        public IEvaluable ItemsBeforeDot
        {
            get
            {
                var expCount = ValuesExpanded.Count();
                if (expCount == 2)
                    return ValuesExpanded.First();
                else
                    return Pair.CreateList(ValuesExpanded.Take(expCount - 1));
            }
        }

        public IEvaluable ItemAfterDot
        {
            get { return ValuesExpanded.Last(); }
        }

        #endregion

        public override string ToString()
        {
            if (this.IsNull())
                return "()";

            if (this.IsTaggedList("PROCEDURE"))
                return "#<interpreted-procedure>";

            if (this.IsTaggedList("CONTINUATION"))
                return "#<continuation>";

            var vals = this.ValuesExpanded;
            var count = vals.Count() - 2;
            var result = "(" + vals.First();
            foreach (var v in vals.Skip(1).Take(count))
                result += " " + v;

            if (vals.Last().IsNull())
                result += ")";
            else
                result += " . " + vals.Last() + ")";

            return result;
        }
    }

    public class Vector : IEvaluable
    {
        public List<IEvaluable> Values { get; private set; }

        public Vector(IEnumerable<IEvaluable> v) { Values = new List<IEvaluable>(v); }

        public override string ToString()
        {
            var s = new StringBuilder();
            s.Append("#(");
            foreach (var v in Values)
            {
                s.Append(v.ToString());
                s.Append(" ");
            }

            var result = s.ToString();
            result = result.Substring(0, result.Length - 1) + ")";
            return result;
        }
    }
    
    #endregion
}